<?php
class fwLoginWll
{
	private static $appId = null;
	private static $securityAlgorithm = null;
	private static $_cryptkey = null;
	private static $_signkey = null;

	private static $loaded = false;

	private static function initVars()
	{
		global $conf;

		if(self::$loaded)
		{
			return;
		}

		self::$appId = $conf['login']['wll']['appid'];
		self::$securityAlgorithm = @$conf['login']['wll']['securityalgorithm'];

		self::$_cryptkey = self::derive($conf['login']['wll']['secret'], "ENCRYPTION");
		self::$_signkey = self::derive($conf['login']['wll']['secret'], "SIGNATURE");
		self::$loaded = true;
	}

	public static function getLoginUrl($context=null)
	{
		self::initVars();

		$url  = 'http://login.live.com/wlogin.srf?appid=' . self::$appId . '&alg=' . self::$securityAlgorithm;
		if($context!==null)
		{
			$url .= '&appctx=' . urlencode($context);
		}
		return $url;
	}

	public static function getLogoutUrl()
	{
		self::initVars();

		return 'http://login.live.com/logout.srf?appid=' . self::$appId;
	}

	public function processLogin($datas)
	{
		self::initVars();

		$token  = $datas['stoken'];
		$context = urldecode($datas['appctx']);

		return self::processToken($token, $context);
	}

	private function processToken($token, $context=null)
	{
		if (!$token)
		{
			return array(false, 'processToken: Invalid token specified.');
		}
		list($responseOk, $decodedToken) = self::decodeToken($token);
		if (!$responseOk)
		{
			return array(false, 'processToken: Failed to decode token: '.$token."\n".$validatedToken);
		}

		list($responseOk, $validatedToken) = self::validateToken($decodedToken);
		if (!$responseOk)
		{
			return array(false, 'processToken: Failed to validate token: '.$decodedToken."\n".$validatedToken);
		}

		list($responseOk, $parsedToken) = self::parse($validatedToken);
		if (!$responseOk)
		{
			return array(false, 'processToken: Failed to parse token after decoding: '.$validatedToken."\n".$parsedToken);
		}

		$appid = self::$appId;
		$tokenappid = $parsedToken['appid'];
		if ($appid != $tokenappid)
		{
			return array(false, 'processToken: Application ID in token did not match ours: '.$tokenappid.', '.$appid);
		}

		list($responseOk, $userId, $usePersistentCookie) = self::validateParsedToken($parsedToken);
		if (!$responseOk)
		{
			return array(false, 'processToken: Failed to validate parsed token: '.$parsedToken."\n".$userId);
		}

		return array(true, $userId, $usePersistentCookie);
	}

	private function validateParsedToken($parsedToken)
	{
		if (!$parsedToken['ts'])
		{
			return array(false, 'Error: extWllUser: Null timestamp.');
		}
		if (!preg_match('/^\d+$/', $parsedToken['ts']) || ($parsedToken['ts'] <= 0))
		{
			return array(false, 'Error: extWllUser: Invalid timestamp: ' . $parsedToken['ts']);
		}

		if (!$parsedToken['uid'])
		{
			return array(false, 'Error: extWllUser: Null id.');
		}
		if (!preg_match('/^\w+$/', $parsedToken['uid'])) {
			return array(false, 'Error: extWllUser: Invalid id: ' . $parsedToken['uid']);
		}

		$usePersistentCookie = (preg_match('/^\d+$/', $parsedToken['flags'])?true:false);
		
		return array(true, $parsedToken['uid'], $usePersistentCookie);
	}

	private function decodeToken($token)
	{
		$ivLen = 16;
		$token = self::u64($token);
		$len = strlen($token);

		if (!$token || ($len <= $ivLen) || (($len % $ivLen) != 0))
		{
			return array(false, 'decodeToken: Attempted to decode invalid token');
		}

		$enc     = MCRYPT_RIJNDAEL_128;
		$cryptkey = self::$_cryptkey;
		$crypted = substr($token, 16);
		$mode    = MCRYPT_MODE_CBC;
		$iv      = substr($token, 0, 16);

		return array(true, mcrypt_decrypt($enc, $cryptkey, $crypted, $mode, $iv));
	}

	private function validateToken($token)
	{
		$split = split("&sig=", $token);
		if (count($split) != 2)
		{
			return array(false, 'validateToken: Invalid token: '.$token);
		}
		list($body, $sig) = $split;

		$sig = self::u64($sig);
		if (!$sig)
		{
			return array(false, 'validateToken: Could not extract signature from token.');
		}

		$sig2 = self::signToken($body);
		if (!$sig2)
		{
			return array(false, 'validateToken: Could not generate signature for the token.');
		}


		if ($sig == $sig2)
		{
			return array(true, $token);
		}

		return array(false, 'validateToken: Signature did not match.');
	}

	private function signToken($token)
	{
		$signkey = self::$_signkey;

		return hash_hmac("sha256", $token, $signkey, true);
	}

	private function parse($input)
	{
		$input = split('&', $input);
		$pairs = array();

		foreach ($input as $pair)
		{
			$kv = split('=', $pair);
			if (count($kv) != 2) {
				return array(false, 'parse: Bad input to parse: ' . $pair);
			}
			$pairs[$kv[0]] = $kv[1];
		}

		return array(true, $pairs);
	}


	private function derive($secret, $prefix)
	{
		$keyLen = 16;
		$key = $prefix . $secret;
		$key = mhash(MHASH_SHA256, $key);

		return substr($key, 0, $keyLen);
	}

	private function u64($input)
	{
		if(is_null($input))
		return null;
		return base64_decode(urldecode($input));
	}

	public static function getClearCookieResponse()
	{
		return array('image/gif', base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'));
	}

}
?>